From: Arnaud Rebillout Date: Tue, 7 Apr 2026 01:03:53 +0000 (+0700) Subject: Backport path_find_first_component() X-Git-Tag: archive/raspbian/247.3-7+rpi1+deb11u8^2~7 X-Git-Url: https://dgit.raspbian.org/%22http:/www.example.com/%22mailto:kde%40ewsoftware.de//%22style.css/%22/%22http:/www.example.com/%22mailto:kde%40ewsoftware.de/%22style.css/%22?a=commitdiff_plain;h=31645c408e64d16529e974e363f804b2451a64be;p=systemd.git Backport path_find_first_component() This is a prerequisite to backport path_startswith_full(). path_find_first_component() was introduced in systemd v249, in commit 0ee54dd4e2f: "path-util: introduce path_find_first_component()". The function didn't change since it was introduced: it's still the same code in systemd v260. However the test associated with it was slightly adjusted afterward. This commit backports the fonction and associated unit tests from systemd v260 (ie. with the latest version of the unit tests). Forwarded: not-needed Gbp-Pq: Name CVE-2026-29111-1.patch --- diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 794599a1..d6e66970 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -729,6 +729,90 @@ int fsck_exists(const char *fstype) { return executable_is_good(checker); } +static const char* skip_slash_or_dot(const char *p) { + for (; !isempty(p); p++) { + if (*p == '/') + continue; + if (startswith(p, "./")) { + p++; + continue; + } + break; + } + return p; +} + +int path_find_first_component(const char **p, bool accept_dot_dot, const char **ret) { + const char *q, *first, *end_first, *next; + size_t len; + + assert(p); + + /* When a path is input, then returns the pointer to the first component and its length, and + * move the input pointer to the next component or nul. This skips both over any '/' + * immediately *before* and *after* the first component before returning. + * + * Examples + * Input: p: "//.//aaa///bbbbb/cc" + * Output: p: "bbbbb///cc" + * ret: "aaa///bbbbb/cc" + * return value: 3 (== strlen("aaa")) + * + * Input: p: "aaa//" + * Output: p: (pointer to NUL) + * ret: "aaa//" + * return value: 3 (== strlen("aaa")) + * + * Input: p: "/", ".", "" + * Output: p: (pointer to NUL) + * ret: NULL + * return value: 0 + * + * Input: p: NULL + * Output: p: NULL + * ret: NULL + * return value: 0 + * + * Input: p: "(too long component)" + * Output: return value: -EINVAL + * + * (when accept_dot_dot is false) + * Input: p: "//..//aaa///bbbbb/cc" + * Output: return value: -EINVAL + */ + + q = *p; + + first = skip_slash_or_dot(q); + if (isempty(first)) { + *p = first; + if (ret) + *ret = NULL; + return 0; + } + if (streq(first, ".")) { + *p = first + 1; + if (ret) + *ret = NULL; + return 0; + } + + end_first = strchrnul(first, '/'); + len = end_first - first; + + if (len > NAME_MAX) + return -EINVAL; + if (!accept_dot_dot && len == 2 && first[0] == '.' && first[1] == '.') + return -EINVAL; + + next = skip_slash_or_dot(end_first); + + *p = next + streq(next, "."); + if (ret) + *ret = first; + return len; +} + int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg) { char *p; int r; diff --git a/src/basic/path-util.h b/src/basic/path-util.h index d613709f..af82624e 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -147,6 +147,7 @@ int fsck_exists(const char *fstype); int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg); char* dirname_malloc(const char *path); +int path_find_first_component(const char **p, bool accept_dot_dot, const char **ret); const char *last_path_component(const char *path); int path_extract_filename(const char *p, char **ret); diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index cb91a1a9..699aacef 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -503,6 +503,98 @@ static void test_file_in_same_dir(void) { free(t); } +static void test_path_find_first_component_one( + const char *path, + bool accept_dot_dot, + char **expected, + int ret) { + + log_debug("/* %s(\"%s\", accept_dot_dot=%s) */", __func__, strnull(path), yes_no(accept_dot_dot)); + + for (const char *p = path;;) { + const char *e; + int r; + + r = path_find_first_component(&p, accept_dot_dot, &e); + if (r <= 0) { + if (r == 0) { + if (path) { + assert_se(p == path + strlen_ptr(path)); + assert_se(isempty(p)); + } else + assert_se(!p); + assert_se(!e); + } + assert_se(r == ret); + assert_se(strv_isempty(expected)); + return; + } + + assert_se(e); + assert_se(strcspn(e, "/") == (size_t) r); + assert_se(strlen_ptr(*expected) == (size_t) r); + assert_se(strneq(e, *expected++, r)); + + assert_se(p); + log_debug("p=%s", p); + if (!isempty(*expected)) + assert_se(startswith(p, *expected)); + else if (ret >= 0) { + assert_se(p == path + strlen_ptr(path)); + assert_se(isempty(p)); + } + } +} + +static void test_path_find_first_component(void) { + _cleanup_free_ char *hoge = NULL; + char foo[NAME_MAX * 2]; + + test_path_find_first_component_one(NULL, false, NULL, 0); + test_path_find_first_component_one("", false, NULL, 0); + test_path_find_first_component_one("/", false, NULL, 0); + test_path_find_first_component_one(".", false, NULL, 0); + test_path_find_first_component_one("./", false, NULL, 0); + test_path_find_first_component_one("./.", false, NULL, 0); + test_path_find_first_component_one("..", false, NULL, -EINVAL); + test_path_find_first_component_one("/..", false, NULL, -EINVAL); + test_path_find_first_component_one("./..", false, NULL, -EINVAL); + test_path_find_first_component_one("////./././//.", false, NULL, 0); + test_path_find_first_component_one("a/b/c", false, STRV_MAKE("a", "b", "c"), 0); + test_path_find_first_component_one("././//.///aa/bbb//./ccc", false, STRV_MAKE("aa", "bbb", "ccc"), 0); + test_path_find_first_component_one("././//.///aa/.../../bbb//./ccc/.", false, STRV_MAKE("aa", "..."), -EINVAL); + test_path_find_first_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", false, STRV_MAKE("aaa", ".bbb"), -EINVAL); + test_path_find_first_component_one("a/foo./b//././/", false, STRV_MAKE("a", "foo.", "b"), 0); + + test_path_find_first_component_one(NULL, true, NULL, 0); + test_path_find_first_component_one("", true, NULL, 0); + test_path_find_first_component_one("/", true, NULL, 0); + test_path_find_first_component_one(".", true, NULL, 0); + test_path_find_first_component_one("./", true, NULL, 0); + test_path_find_first_component_one("./.", true, NULL, 0); + test_path_find_first_component_one("..", true, STRV_MAKE(".."), 0); + test_path_find_first_component_one("/..", true, STRV_MAKE(".."), 0); + test_path_find_first_component_one("./..", true, STRV_MAKE(".."), 0); + test_path_find_first_component_one("////./././//.", true, NULL, 0); + test_path_find_first_component_one("a/b/c", true, STRV_MAKE("a", "b", "c"), 0); + test_path_find_first_component_one("././//.///aa/bbb//./ccc", true, STRV_MAKE("aa", "bbb", "ccc"), 0); + test_path_find_first_component_one("././//.///aa/.../../bbb//./ccc/.", true, STRV_MAKE("aa", "...", "..", "bbb", "ccc"), 0); + test_path_find_first_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", true, STRV_MAKE("aaa", ".bbb", "..", "c.", "d.dd", "..eeee"), 0); + test_path_find_first_component_one("a/foo./b//././/", true, STRV_MAKE("a", "foo.", "b"), 0); + + memset(foo, 'a', sizeof(foo) -1); + char_array_0(foo); + + test_path_find_first_component_one(foo, false, NULL, -EINVAL); + test_path_find_first_component_one(foo, true, NULL, -EINVAL); + + hoge = strjoin("a/b/c/", foo, "//d/e/.//f/"); + assert_se(hoge); + + test_path_find_first_component_one(hoge, false, STRV_MAKE("a", "b", "c"), -EINVAL); + test_path_find_first_component_one(hoge, true, STRV_MAKE("a", "b", "c"), -EINVAL); +} + static void test_last_path_component(void) { assert_se(last_path_component(NULL) == NULL); assert_se(streq(last_path_component("a/b/c"), "c")); @@ -718,6 +810,7 @@ int main(int argc, char **argv) { test_path_startswith(); test_prefix_root(); test_file_in_same_dir(); + test_path_find_first_component(); test_last_path_component(); test_path_extract_filename(); test_filename_is_valid();